#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libavutil/time.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>

//https://blog.csdn.net/qq_42161913/article/details/140159226

int main(void)
{
    const char *input_format_name = "v4l2";           // 输入格式名称，Linux下为video4linux2或v4l2
    const char *device_name = "/dev/video0";                  // 摄像头设备名称
    const char *camera_resolution = "640x480";                // 摄像头分辨率
    enum AVPixelFormat camera_pix_fmt = AV_PIX_FMT_YUYV422;   // 摄像头像素格式
    const char *url = "udp://192.168.1.105:8080"; // udp地址
    int frame_rate = 25;                                      // 帧率
    int ret = -1;
    int video_streamid = -1;
    int64_t frame_index = 0;
    AVDictionary *options = NULL;
    AVInputFormat *fmt = NULL;
    AVFormatContext *in_context = NULL, *out_context = NULL;
    struct SwsContext *sws_ctx = NULL;
    AVCodecContext *codec_context = NULL;
    AVStream *out_stream = NULL;
    AVCodec *codec = NULL;
 
    // 打印ffmpeg版本信息
    printf("ffmpeg version: %s\n", av_version_info());
 
    // 注册所有设备
    avdevice_register_all();

    avformat_network_init();

    // 查找输入格式
    fmt = av_find_input_format(input_format_name);
    if (!fmt)
    {
        printf("av_find_input_format error");
        return -1;
    }
 
    // 设置分辨率
    av_dict_set(&options, "video_size", camera_resolution, 0);
 
    // 打开输入流并初始化格式上下文
    ret = avformat_open_input(&in_context, device_name, fmt, &options);
    if (ret != 0)
    {
        // 错误的时候释放options，成功的话 avformat_open_input 内部会释放
        av_dict_free(&options);
        printf("avformat_open_input error");
        return -1;
    }
 
    // 查找流信息
    if (avformat_find_stream_info(in_context, 0) < 0)
    {
        printf("avformat_find_stream_info failed\n");
        goto end;
    }
 
    // 查找视频流索引
    video_streamid = av_find_best_stream(in_context, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (video_streamid < 0)
    {
        printf("cannot find video stream");
        goto end;
    }
    AVStream *video_stream = in_context->streams[video_streamid];
    printf("input stream, width: %d, height: %d, format: %s\n",
           video_stream->codecpar->width, video_stream->codecpar->height,
           av_get_pix_fmt_name((enum AVPixelFormat)video_stream->codecpar->format));
 
    // 检查实际获取到的格式是否为设置的摄像头像素格式
    if (video_stream->codecpar->format != camera_pix_fmt)
    {
        printf("pixel format error");
        goto end;
    }
 
    // 初始化转换上下文
    sws_ctx = sws_getContext(
        video_stream->codecpar->width, video_stream->codecpar->height, camera_pix_fmt,
        video_stream->codecpar->width, video_stream->codecpar->height, AV_PIX_FMT_YUV420P,
        SWS_BILINEAR, NULL, NULL, NULL);
    if (!sws_ctx)
    {
        printf("sws_getContext error\n");
        goto end;
    }
 
    // 分配输出格式上下文
    // avformat_alloc_output_context2(&out_context, NULL, "flv", NULL);
    avformat_alloc_output_context2(&out_context, NULL, "h264", NULL);
    if (!out_context)
    {
        printf("avformat_alloc_output_context2 failed\n");
        goto end;
    }
 
    // 查找编码器
    codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec)
    {
        printf("Codec not found\n");
        goto end;
    }
    printf("codec name: %s\n", codec->name);
 
    // 创建新的视频流
    out_stream = avformat_new_stream(out_context, NULL);
    if (!out_stream)
    {
        printf("avformat_new_stream failed\n");
        goto end;
    }
 
    // 分配编码器上下文
    codec_context = avcodec_alloc_context3(codec);
    if (!codec_context)
    {
        printf("avcodec_alloc_context3 failed\n");
        goto end;
    }
 
    // 设置编码器参数
    codec_context->codec_id = AV_CODEC_ID_H264;
    codec_context->codec_type = AVMEDIA_TYPE_VIDEO;
    codec_context->pix_fmt = AV_PIX_FMT_YUV420P;
    codec_context->width = video_stream->codecpar->width;
    codec_context->height = video_stream->codecpar->height;
    codec_context->time_base = (AVRational){1, frame_rate};         // 设置时间基
    codec_context->framerate = (AVRational){frame_rate, 1};         // 设置帧率
    codec_context->bit_rate = 750 * 1000;                           // 设置比特率
    codec_context->gop_size = frame_rate;                           // 设置GOP大小
    codec_context->max_b_frames = 0;                                // 设置最大B帧数，不需要B帧时设置为0
    av_opt_set(codec_context->priv_data, "profile", "baseline", 0); // 设置h264画质级别
    av_opt_set(codec_context->priv_data, "tune", "zerolatency", 0); // 设置h264编码优化参数
    // 检测输出上下文的封装格式，判断是否设置 AV_CODEC_FLAG_GLOBAL_HEADER
    // AV_CODEC_FLAG_GLOBAL_HEADER：由原来编码时在每个关键帧前加入pps和sps，改变为在extradate这个字节区加入pps和sps
    if (out_context->oformat->flags & AVFMT_GLOBALHEADER)
    {
        printf("set AV_CODEC_FLAG_GLOBAL_HEADER\n");
        codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }
 
    // 打开编码器
    if (avcodec_open2(codec_context, codec, NULL) < 0)
    {
        printf("avcodec_open2 failed\n");
        goto end;
    }
 
    // 将编码器参数复制到流
    ret = avcodec_parameters_from_context(out_stream->codecpar, codec_context);
    if (ret < 0)
    {
        printf("avcodec_parameters_from_context failed\n");
        goto end;
    }
 
    // 分配内存
    AVFrame *input_frame = av_frame_alloc();
    AVFrame *frame_yuv420p = av_frame_alloc();
    if (!input_frame || !frame_yuv420p)
    {
        printf("av_frame_alloc error\n");
        goto end;
    }
    AVPacket *packet = av_packet_alloc();
    if (!packet)
    {
        printf("av_packet_alloc failed\n");
        goto end;
    }
 
    // 设置帧格式
    input_frame->format = camera_pix_fmt;
    input_frame->width = video_stream->codecpar->width;
    input_frame->height = video_stream->codecpar->height;
 
    frame_yuv420p->format = AV_PIX_FMT_YUV420P;
    frame_yuv420p->width = video_stream->codecpar->width;
    frame_yuv420p->height = video_stream->codecpar->height;
 
    // 分配帧内存
    ret = av_frame_get_buffer(frame_yuv420p, 0);
    if (ret < 0)
    {
        printf("av_frame_get_buffer error\n");
        goto end;
    }
 
    // 打开url
    if (!(out_context->oformat->flags & AVFMT_NOFILE))
    {
        ret = avio_open(&out_context->pb, url, AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            printf("avio_open error (errmsg '%s')\n", av_err2str(ret));
            goto end;
        }
    }
 
    // 写文件头
    ret = avformat_write_header(out_context, NULL);
    if (ret < 0)
    {
        printf("avformat_write_header failed\n");
        goto end;
    }
 
    // 读取帧并进行转换
    AVPacket pkt;
    while (av_read_frame(in_context, &pkt) >= 0)
    {
        if (pkt.stream_index == video_streamid)
        {
            // 把读取的帧数据(AVPacket)拷贝到输入帧(AVFrame)中
            ret = av_image_fill_arrays(input_frame->data, input_frame->linesize, pkt.data, camera_pix_fmt,
                                       video_stream->codecpar->width, video_stream->codecpar->height, 1);
            if (ret < 0)
            {
                av_packet_unref(&pkt);
                printf("av_image_fill_arrays error\n");
                break;
            }
 
            // 转换为 YUV420P
            sws_scale(sws_ctx, (const uint8_t *const *)input_frame->data, input_frame->linesize, 0,
                      input_frame->height, frame_yuv420p->data, frame_yuv420p->linesize);
 
            frame_yuv420p->pts = frame_index;
            frame_index++;
            // 发送帧到编码器
            ret = avcodec_send_frame(codec_context, frame_yuv420p);
            if (ret < 0)
            {
                printf("avcodec_send_frame error (errmsg '%s')\n", av_err2str(ret));
                break;
            }
 
            // 接收编码后的数据包
            while (ret >= 0)
            {
                ret = avcodec_receive_packet(codec_context, packet);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                {
                    break;
                }
                else if (ret < 0)
                {
                    printf("avcodec_receive_packet error (errmsg '%s')\n", av_err2str(ret));
                    goto end;
                }
 
                packet->stream_index = out_stream->index;
                // 将时间戳从编码器时间基转换到流时间基
                av_packet_rescale_ts(packet, codec_context->time_base, out_stream->time_base);
                packet->pos = -1;
                // 推送到RTMP服务器
                ret = av_interleaved_write_frame(out_context, packet);
                if (ret < 0)
                {
                    printf("av_interleaved_write_frame error (errmsg '%d')\n", ret);
                    av_packet_unref(packet);
                    goto end;
                }
 
                av_packet_unref(packet);
            }
        }
        av_packet_unref(&pkt);
    }
 
end:
    // 释放资源
    if (input_frame)
        av_frame_free(&input_frame);
    if (frame_yuv420p)
        av_frame_free(&frame_yuv420p);
    if (sws_ctx)
        sws_freeContext(sws_ctx);
    if (in_context)
        avformat_close_input(&in_context);
    if (packet)
        av_packet_free(&packet);
    if (codec_context)
        avcodec_free_context(&codec_context);
    if (out_context && !(out_context->flags & AVFMT_NOFILE))
        avio_close(out_context->pb);
    if (out_context)
        avformat_free_context(out_context);
 
    return 0;
}


